package de.sendorian.app.forumArchive;


import java.io.IOException;
import java.util.List;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;

import org.apache.log4j.Logger;
import org.joda.time.DateTime;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.stereotype.Component;

import com.google.common.base.Predicate;
import com.google.common.collect.Iterators;

import de.sendorian.app.forumArchive.domain.Forum;
import de.sendorian.app.forumArchive.domain.Post;
import de.sendorian.app.forumArchive.domain.Topic;
import de.sendorian.app.forumArchive.download.Downloader;
import de.sendorian.app.forumArchive.finder.Finder;
import de.sendorian.app.forumArchive.repository.ForumRepository;

/**
 * Central class that manages the indexing and downloading.
 *
 * @author sendorian
 *
 */
@Component
public class ArchiveBuilder implements Runnable {

	private static Logger LOG = Logger.getLogger(ArchiveBuilder.class);

	private ForumRepository forumRepository;
	private Finder<String, Forum> forumFinder;
	private Finder<Forum, Topic> topicFinder;
	Finder<Topic, Post> postFinder;
	private String baseUrl;
	private Downloader downloader;
	private Compressor compressor;
	private Cleaner cleaner;

	public static void main(String[] args) throws IOException {
		ApplicationContext context = new ClassPathXmlApplicationContext("application-context.xml");
		Runnable mainClass = (Runnable) context.getBean("archiveBuilder");
		mainClass.run();
	}
	

	public void run() {
		List<Forum> forums = forumFinder.find(baseUrl);
		for (Forum forum : forums) {
			Forum persistenForum = forumRepository.findByName(forum.getName());
			if (persistenForum != null) {
				forum = persistenForum;
			}

			LOG.info("Finding topics for forum \"" + forum.getName() + "\"");
			List<Topic> topics = topicFinder.find(forum);

			for (Topic topic : topics) {
				if (hasToBeUpdated(forum, topic)) {
					updateTopic(topic, forum);
				}
			}
			forumRepository.saveOrUpdate(forum);
		}

		forums = forumRepository.findAll();
		downloader.downloadForums(forums);
		compressor.compressImages(forums);
		cleaner.cleanup();

		forumRepository.shutdownDatabase();
		LOG.info("Finished!");
	}


	/**
	 * Returns true if the topic is not yet persistent or outdated.
	 *
	 */
	private boolean hasToBeUpdated(@Nonnull final Forum forum, @Nullable final Topic topic) {
		Predicate<Topic> uptodatePredicate = new Predicate<Topic>() {
			public boolean apply(Topic persistentTopic) {
				if (!persistentTopic.equals(topic)) {
					return false;
				}

				DateTime persistentLastChange = new DateTime(persistentTopic.getLastPost());
				DateTime onlineLastChange = new DateTime(topic.getLastPost());
				if (onlineLastChange.isAfter(persistentLastChange)) {
					LOG.debug("Topic \"" + topic.getName() + "\" is outdated (DB: "
					    + persistentLastChange + ", Online: " + onlineLastChange + ").");
					return false;
				}
				LOG.info("Topic \"" + topic.getName() + "\" is uptodate.");
				return true;
			}
		};
		return !Iterators.any(forum.getTopics().iterator(), uptodatePredicate);
	}


	/**
	 * Updates the outdated Topic in the forum or adds it to the forums' topics if it was not
	 * persisted before.
	 *
	 */
    private void updateTopic(@Nonnull Topic topic, @Nonnull Forum forum) {
	    LOG.info("Updating posts for topic \"" + topic.getName() + "\"");
	    List<Post> posts = postFinder.find(topic);
	    topic.setPosts(posts);
	    forum.getTopics().remove(topic);
		forum.getTopics().add(topic);
    }
    
    
    @Value("#{config.baseUrl}")
	public void setBaseUrl(String baseUrl) {
		this.baseUrl = baseUrl;
	}
	
	@Autowired
	public void setForumRepository(ForumRepository forumRepository) {
		this.forumRepository = forumRepository;
	}

	@Value("#{config.forumFinder}")
	public void setForumFinder(Finder<String, Forum> forumFinder) {
		this.forumFinder = forumFinder;
	}

	@Value("#{config.topicFinder}")
	public void setTopicFinder(Finder<Forum, Topic> topicFinder) {
		this.topicFinder = topicFinder;
	}
	
	@Value("#{config.postFinder}")
	public void setPostFinder(Finder<Topic, Post> postFinder) {
		this.postFinder = postFinder;
	}

	@Autowired
	public void setDownloader(Downloader downloader) {
		this.downloader = downloader;
	}

	@Autowired
	public void setCompressor(Compressor compressor) {
		this.compressor = compressor;
	}

	@Autowired
	public void setCleaner(Cleaner cleaner) {
		this.cleaner = cleaner;
	}
}
